/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.persistence.admin;

import java.util.*;
import java.io.PrintWriter;
import java.text.*;

/**
  * Provides single point for all log messages to written to.
  * Currently this class only supports static methods and always
  * writes to stdout.
  *
  */
public class Logger
{
    private static final PrintWriter logWriter = new PrintWriter(System.out, true);

    // Set LOGWIDTH to maxint as a cheap way of turning off formatting
    private static final int LOGWIDTH = Integer.MAX_VALUE;
    private static final SimpleDateFormat timeFormatter;
    static {
	final String defaultFormatPattern = "MM/dd/yy HH:mm:ss.SSS z";
	final String resourceName = "org.apache.persistence.admin.LoggerResources";
	final String keyName = "logger.timeStampFormat";
	String formatPattern = defaultFormatPattern;
	SimpleDateFormat sdf;
        try {
	    ResourceBundle messageRB =
                ResourceBundle.getBundle(resourceName);
	    try {
		formatPattern = messageRB.getString(keyName);
	    } catch (MissingResourceException e) {
		System.out.println("NOTICE: Logger using default timestamp format."
				   + " Could not get resource key \""
				   + keyName
				   + "\" because: " + e);
	    }
        } catch (MissingResourceException e) {
	    System.out.println("NOTICE: Logger using default timestamp format."
			       + " Could not load resource bundle \""
			       + resourceName
			       + "\" because: " + e);
        }
	if (formatPattern.length() == 0) {
	    sdf = null;
	} else {
	    try {
		sdf = new SimpleDateFormat(formatPattern);
	    } catch (RuntimeException e) {
		System.out.println("NOTICE: ignoring timestamp pattern \""
				   + formatPattern
				   + "\" because: " + e.toString());
		System.out.println("  Using default pattern: \""
				   + defaultFormatPattern + "\".");
		formatPattern = defaultFormatPattern;
		sdf = new SimpleDateFormat(formatPattern);
	    }
	}
	timeFormatter = sdf;
    }

    static private void formatText(PrintWriter writer, String target,
				   int maxLength, int initialLength) {
	BreakIterator boundary = BreakIterator.getLineInstance();
	boundary.setText(target);
	int start = boundary.first();
	int end = boundary.next();
	int lineLength = initialLength;

	while (end != BreakIterator.DONE) {
	    // Look at the end and only accept whitespace breaks
	    char endChar = target.charAt(end-1);
	    while (!Character.isWhitespace(endChar)) {
		int lastEnd = end;
		end = boundary.next();
		if (end == BreakIterator.DONE) {
		    // give up. We are at the end of the string
		    end = lastEnd;
		    break;
		}
		endChar = target.charAt(end-1);
	    }
	    int wordEnd = end;
	    if (endChar == '\n') {
		// trim off the \n since println will do it for us
		wordEnd--;
	    } else if (endChar == '\t') {
		// figure tabs use 8 characters
		lineLength += 7;
	    }
	    String word = target.substring(start, wordEnd);
	    if ((lineLength + word.length()) >= maxLength) {
		if (lineLength != 0) {
		    writer.println();
		    writer.print("  ");
		    lineLength = 2;
		}
	    }
	    lineLength += word.length();
	    writer.print(word);
	    if (endChar == '\n') {
		// force end of line
		writer.println();
		writer.print("  ");
		lineLength = 2;
	    }
	    start = end;
	    end = boundary.next();
	}
	if (lineLength != 0) {
	    writer.println();
	}
    }

  /**
   * Gets a String representation of the current time.
   * @return a String representation of the current time.
   */
  static public String getTimeStamp() {
      return formatDate(new Date());
  }
  /**
   * Convert a Date to a timestamp String.
   * @param d a Date to format as a timestamp String.
   * @return a String representation of the current time.
   */
  static public String formatDate(Date d) {
      if (timeFormatter == null) {
	  try {
	      // very simple format that shows millisecond resolution
	      return Long.toString(d.getTime());
	  } catch (Exception ignore) {
	      return "timestampFormatFailed";
	  }
      }
      try {
	  synchronized (timeFormatter) {
	      // Need sync: see bug 21858
	      return timeFormatter.format(d);
	  }
      } catch (Exception e1) {
	  // Fix bug 21857
	  try {
	      return d.toString();
	  } catch (Exception e2) {
	      try {
		  return Long.toString(d.getTime());
	      } catch (Exception e3) {
		  return "timestampFormatFailed";
	      }
	  }
      }
  }

  /**
    * Logs a message to the static log destination.
    * @param msg the actual message to log
    */
  static public void put(String msg) {
      put(msg, (Throwable)null);
  }
    
  /**
    * Logs a message to the specified log destination.
    * @param log the <code>PrintWriter</code> that the message will be written to.
    * @param msg the actual message to log
    */
  static public void put(PrintWriter log, String msg) {
      put(log, msg, (Throwable)null);
  }

  /**
    * Logs an exception to the static log destination.
    * @param exception the actual Exception to log
    */
  static public void put(Throwable exception) {
      put((String)null, exception);
  }
  /**
    * Logs an exception to the specified log destination.
    * @param log the <code>PrintWriter</code> that the message will be written to.
    * @param exception the actual Exception to log
    */
  static public void put(PrintWriter log, Throwable exception) {
      put(log, (String)null, exception);
  }

  /**
    * Logs a message and an exception to the static log destination.
    * @param msg the actual message to log
    * @param exception the actual Exception to log
    */
  static public void put(String msg, Throwable exception) {
      put(logWriter, msg, exception);
  }
    
  /**
    * Logs a message and an exception to the specified log destination.
    * @param log the <code>PrintWriter</code> that the message will be written to. If null then the default stdout writer is used.
    * @param msg the actual message to log
    * @param exception the actual Exception to log
    */
  static public void put(PrintWriter log, String msg, Throwable exception) {
      java.io.StringWriter sw = new java.io.StringWriter();
      String header;
      PrintWriter pw = new PrintWriter(sw);
      
      pw.println();
      header = '[' + getTimeStamp() + ' ' + Thread.currentThread().getName() + "] ";
      pw.print(header);
      if (msg != null) {
	  try {
	      formatText(pw, msg, LOGWIDTH, header.length());
	  } catch (RuntimeException e) {
	      pw.println(msg);
	      pw.println("Ignoring exception:");
	      e.printStackTrace(pw);
	  }
      } else {
	  pw.println();
      }
      if (exception != null) {
	  exception.printStackTrace(pw);
      }
      pw.close();
      try {
	  sw.close();
      } catch (java.io.IOException ignore) {}

      if (log == null) {
	  log = logWriter;
      }
      log.print(sw.toString());
      log.flush();
  }

  /**
   * Formats a message.  Takes special care when invoking the
   * toString() method of objects that might cause NPEs.
   */
  public static String format(String format, Object[] objs) {
    String[] strings = new String[objs.length];
    for (int i = 0; i < objs.length; i++) {
      Object obj = objs[i];
      if (obj == null) {
        strings[i] = "null";

      } else {
        try {
          strings[i] = obj.toString();

        } catch (Exception ex) {
          strings[i] = obj.getClass().getName() + "@" +
            System.identityHashCode(obj);
        }
      }
    }

    return java.text.MessageFormat.format(format, (Object[])strings);
  }

}
